/*! MoonPdfLib - Provides a WPF user control to display PDF files
Copyright (C) 2013  (see AUTHORS file)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
!*/
/*
 * 2013 - Modified version of Paul McClean's code (see AUTHORS file)
 */
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;

namespace MoonPdfLib.Virtualizing
{
	/// <summary>
	/// Derived VirtualizatingCollection, performing loading asychronously.
	/// </summary>
	/// <typeparam name="T">The type of items in the collection</typeparam>
	internal class AsyncVirtualizingCollection<T> : VirtualizingCollection<T>, INotifyCollectionChanged, INotifyPropertyChanged
	{
		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="AsyncVirtualizingCollection&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="itemsProvider">The items provider.</param>
		public AsyncVirtualizingCollection(IItemsProvider<T> itemsProvider)
			: base(itemsProvider)
		{
			_synchronizationContext = SynchronizationContext.Current;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AsyncVirtualizingCollection&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="itemsProvider">The items provider.</param>
		/// <param name="pageSize">Size of the page.</param>
		public AsyncVirtualizingCollection(IItemsProvider<T> itemsProvider, int pageSize)
			: base(itemsProvider, pageSize)
		{
			_synchronizationContext = SynchronizationContext.Current;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AsyncVirtualizingCollection&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="itemsProvider">The items provider.</param>
		/// <param name="pageSize">Size of the page.</param>
		/// <param name="pageTimeout">The page timeout.</param>
		public AsyncVirtualizingCollection(IItemsProvider<T> itemsProvider, int pageSize, TimeSpan pageTimeout)
			: base(itemsProvider, pageSize, pageTimeout)
		{
			_synchronizationContext = SynchronizationContext.Current;
		}

		#endregion

		#region SynchronizationContext

		private readonly SynchronizationContext _synchronizationContext;

		/// <summary>
		/// Gets the synchronization context used for UI-related operations. This is obtained as
		/// the current SynchronizationContext when the AsyncVirtualizingCollection is created.
		/// </summary>
		/// <value>The synchronization context.</value>
		protected SynchronizationContext SynchronizationContext
		{
			get { return _synchronizationContext; }
		}

		#endregion

		#region INotifyCollectionChanged

		/// <summary>
		/// Occurs when the collection changes.
		/// </summary>
		public event NotifyCollectionChangedEventHandler CollectionChanged;

		/// <summary>
		/// Raises the <see cref="E:CollectionChanged"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
		protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
		{
			NotifyCollectionChangedEventHandler h = CollectionChanged;
			if (h != null)
				h(this, e);
		}

		/// <summary>
		/// Fires the collection reset event.
		/// </summary>
		private void FireCollectionReset()
		{
			NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
			OnCollectionChanged(e);
		}

		#endregion

		#region INotifyPropertyChanged

		/// <summary>
		/// Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Raises the <see cref="E:PropertyChanged"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
		protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
		{
			PropertyChangedEventHandler h = PropertyChanged;
			if (h != null)
				h(this, e);
		}

		/// <summary>
		/// Fires the property changed event.
		/// </summary>
		/// <param name="propertyName">Name of the property.</param>
		private void FirePropertyChanged(string propertyName)
		{
			PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
			OnPropertyChanged(e);
		}

		#endregion

		#region IsLoading

		private bool _isLoading;

		/// <summary>
		/// Gets or sets a value indicating whether the collection is loading.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this collection is loading; otherwise, <c>false</c>.
		/// </value>
		public bool IsLoading
		{
			get
			{
				return _isLoading;
			}
			set
			{
				if (value != _isLoading)
				{
					_isLoading = value;
					FirePropertyChanged("IsLoading");
				}
			}
		}

		#endregion

		#region Load overrides

		/// <summary>
		/// Asynchronously loads the count of items.
		/// </summary>
		protected override void LoadCount()
		{
			Count = 0;
			IsLoading = true;
			ThreadPool.QueueUserWorkItem(LoadCountWork);
		}

		/// <summary>
		/// Performed on background thread.
		/// </summary>
		/// <param name="args">None required.</param>
		private void LoadCountWork(object args)
		{
			int count = FetchCount();
			SynchronizationContext.Send(LoadCountCompleted, count);
		}

		/// <summary>
		/// Performed on UI-thread after LoadCountWork.
		/// </summary>
		/// <param name="args">Number of items returned.</param>
		private void LoadCountCompleted(object args)
		{
			Count = (int)args;
			IsLoading = false;
			FireCollectionReset();
		}

		/// <summary>
		/// Asynchronously loads the page.
		/// </summary>
		/// <param name="index">The index.</param>
		protected override void LoadPage(int index)
		{
			IsLoading = true;
			ThreadPool.QueueUserWorkItem(LoadPageWork, index);
		}

		/// <summary>
		/// Performed on background thread.
		/// </summary>
		/// <param name="args">Index of the page to load.</param>
		private void LoadPageWork(object args)
		{
			int pageIndex = (int)args;
			IList<T> page = FetchPage(pageIndex);
			SynchronizationContext.Send(LoadPageCompleted, new object[] { pageIndex, page });
		}

		/// <summary>
		/// Performed on UI-thread after LoadPageWork.
		/// </summary>
		/// <param name="args">object[] { int pageIndex, IList(T) page }</param>
		private void LoadPageCompleted(object args)
		{
			int pageIndex = (int)((object[])args)[0];
			IList<T> page = (IList<T>)((object[])args)[1];

			PopulatePage(pageIndex, page);
			IsLoading = false;
			FireCollectionReset();
		}

		#endregion
	}
}
